<template>
  <div class="viewport" ref="viewport" @scroll="scrollFn">
    <!-- 自己做一个滚动条，为什么? -->
    <div class="scroll-bar" ref="scrollBar"></div>
    <!-- 显示出来的内容 -->
    <div class="scroll-list" :style="{ transform: `translate3d(0, ${offset}px, 0)` }">
      <!-- 这里渲染是我们需要渲染的内容，那么如何传进来呢？如果直接在这里使用的话就写死了。可以复用的话就需要使用slot-->
      <!-- 外部传进来就变得灵活了。 -->
      <div v-for="item in visibleData" :vid="item.id" :key="item.id" ref="items">
        <!-- 将父级的数据传会给item -->
        <slot class="item" :item="item"></slot>
      </div>
    </div>
  </div>
</template>

<script>
import throttle from 'lodash/throttle'
export default {
  props: {
    items: Array,
    remain: Number,
    size: Number,
    variable: Boolean
  },
  data() {
    return {
      start: 0,
      end: this.remain,
      offset: 0,
      positions: []
    }
  },
  created() {
    //   第一次不触发，leading
    this.scrollFn = throttle(this.handleScroll, 200, { leading: false })
  },
  updated() {
    // 就算再这里也拿不到真实渲染的dom

    this.$nextTick(() => {
      // 根据当前显示更新相应的数据，最终更新滚动条高度。
      let nodes = this.$refs.items
      if (!nodes && nodes.length > 0) {
        return
      }
      nodes.forEach((element) => {
        let { height } = element.getBoundingClientRect() // 真实的
        let id = element.getAttribute('vid') - 0 // 转数字,或者是字符串

        let oldHeight = this.positions[id].height
        let val = oldHeight - height // 高度是否有变化
        if (val) {
          this.positions[id].height = height
          this.positions[id].bottom = this.positions[id].bottom - val
          //  当前面变了，后面也要跟着变
          for (let i = id + 1; i < this.positions.length; i++) {
            this.positions[i].top = this.positions[i - 1].bottom
            this.positions[i].bottom = this.positions[i].bottom - val
          }
        }
      })
      //  只要更新   就算出滚动条的宽度。
      this.$refs.scrollBar.style.height = this.positions[this.positions.length - 1].bottom + 'px'
    })
  },
  mounted() {
    // 视口宽度
    this.$refs.viewport.style.height = this.size * this.remain + 'px'
    // 总长度
    this.$refs.scrollBar.style.height = this.size * this.items.length + 'px'

    // 提供给variable使用
    // 如果加载完毕 我需要缓存每一项的属性
    this.cacheList()
  },
  computed: {
    // 渲染多少个的问题
    visibleData() {
      // 这里就需要增加预留的，就是增加可视
      // 为什么要减去呢？ 减去才能截取多个。
      let start = this.start - this.prevCount
      let end = this.end + this.nextCount
      return this.items.slice(start, end)
    },
    prevCount() {
      // 前面还没滚动的先不预留  this.start
      return Math.min(this.start, this.remain)
    },
    nextCount() {
      // 后面不够取remain大小，所以取剩余的。
      return Math.min(this.remain, this.items.length - this.end)
    }
  },
  methods: {
    getStartIndex(value) {
      let start = 0
      let end = this.positions.length - 1
      let temp = null
      while (start <= end) {
        let middleIndex = parseInt((start + end) / 2)
        let middleValue = this.positions[middleIndex].bottom

        if (middleValue == value) {
          return middleIndex + 1
        } else if (middleValue < value) {
          start = middleIndex + 1
        } else if (middleValue > value) {
          // 查范围
          if (temp == null || temp > middleIndex) {
            temp = middleIndex
          }
          end = middleIndex - 1
        }
      }
      return temp
    },
    cacheList() {
      this.positions = this.items.map((item, index) => ({
        height: this.size,
        top: index * this.size,
        bottom: (index + 1) * this.size
      }))
    },
    handleScroll() {
      let scrollTop = this.$refs.viewport.scrollTop
      if (this.variable) {
        //   这是不定宽高计算方法
        //   使用二分查找
        this.start = this.getStartIndex(scrollTop)
        this.end = this.start + this.remain
        this.offset = this.positions[this.start - this.prevCount] ? this.positions[this.start - this.prevCount].top : 0
      } else {
        //  这是定宽高的计算方法

        // 计算需要显示哪些元素,根据scrollTop就行

        this.start = Math.floor(scrollTop / this.size) // 为什么要向下取值，因为滚动的高度可能还一个出现了一半，所以需要再取。没有取的话上面就会空白一块。
        this.end = this.start + this.remain

        // 还有个问题就是定位的问题，因为他一直定位0的位置，但是我们已经滚走了
        this.offset = this.start * this.size - this.prevCount * this.size

        // 这样的话，下面可能会缺点东西。
        // 所以需要预留出来，前面预留点，后面预留点
        // 然后offset还需要 减去 预留的高度。
      }
    }
  }
}
</script>

<style lang="scss">
.viewport {
  overflow-y: scroll;
  position: relative;
}

.scroll-list {
  position: absolute;
  left: 0px;
  top: 0px;
}

.item {
  width: 200px;
  border: 1px solid black;
  box-sizing: border-box;
}
</style>
